package 设计模式.代理模式.dynamicproxy.defproxy;

import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.FileWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

/**
 * @ClassName MyProxy
 * @Description 自定义Proxy工具类
 * @Author wf
 * @Date 2020/5/18 16:37
 * @Version 1.0
 */
public class MyProxy {

    public static final String ln = "\r\n";

    public static Object newProxyInstance(MyClassLoader loader, Class<?>[] interfaces, MyInvocationHandler h) {
        //1.动态生成源码.java文件
        String srcFile = generateSrc(interfaces);
        System.out.println(srcFile);
        //2.输出.java文件到磁盘
        String filePath = MyProxy.class.getResource("").getPath();
        File file = new File(filePath + "$Proxy0.java");
        try {
            FileWriter fw = new FileWriter(file);
            fw.write(srcFile);
            fw.flush();
            fw.close();

            //3.编译.java文件，成$Proxy0.class文件
            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
            StandardJavaFileManager manager = compiler.getStandardFileManager(null, null, null);
            Iterable<? extends JavaFileObject> it = manager.getJavaFileObjects(file);
            JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, null, null, it);
            task.call();
            manager.close();

            //4.把生成的.class文件加载到jvm中
            Class<?> proxyClass = loader.findClass("$Proxy0");
            Constructor<?> constructor = proxyClass.getConstructor(MyInvocationHandler.class);


            //5.返回新的代理对象
            return constructor.newInstance(h);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private static String generateSrc(Class<?>[] interfaces) {
        StringBuilder sb = new StringBuilder();
        sb.append(MyProxy.class.getPackage()).append(";").append(ln);
        sb.append("import ").append(interfaces[0].getName()).append(";").append(ln);
        sb.append("import java.lang.reflect.*;").append(ln);
        sb.append("public final class $Proxy0 implements ").append(interfaces[0].getSimpleName()).append("{").append(ln);
        sb.append("MyInvocationHandler h;").append(ln);
        sb.append("public $Proxy0(MyInvocationHandler h){").append(ln).
                append("    this.h = h;").append(ln).
                append("    }").append(ln);
        for (Method m : interfaces[0].getMethods()) {
            Class<?>[] params = m.getParameterTypes();
            StringBuilder paramNames = new StringBuilder();
            StringBuilder paramValues = new StringBuilder();
            StringBuilder paramClasses = new StringBuilder();

            for (int i = 0; i < params.length; i++) {
                Class clazz = params[i];
                String type = clazz.getName();
                String paramName = toLowerFirstCase(clazz.getSimpleName());
                paramNames.append(type).append(" ").append(paramName);
                paramValues.append(paramName);
                paramClasses.append(clazz.getName()).append(".class");
                if (i < params.length - 1) {
                    paramNames.append(",");
                    paramValues.append(",");
                    paramClasses.append(",");
                }
            }
            sb.append("public ").append(m.getReturnType().getName()).append(" ").append(m.getName()).append("(")
                    .append(paramNames).append("){").append(ln);
            sb.append("     try {").append(ln).
                    append("        Method m = ").append(interfaces[0].getName()).append(".class.getMethod(\"").append(m.getName()).
                    append("\",new Class[]{").append(paramClasses).append("} );").append(ln);
            sb.append("     ").append(hasReturnValue(m.getReturnType()) ? "return (" + m.getReturnType().getTypeName() + ")" : "").
                    append("this.h.invoke(this, m, ").append("new Object[]{").append(paramValues).append("});").append(ln).
                    append("    } catch (RuntimeException | Error var2) {\n").
                    append("            throw var2;\n").
                    append("    } catch (Throwable var3) {\n").
                    append("            throw new UndeclaredThrowableException(var3);\n").
                    append("        }\n");//.append(getReturnEmptyCode(m.getReturnType())).append(ln);
            sb.append("    }");
        }
        return sb.append(ln).append("}").toString();
    }

    private static String getReturnEmptyCode(Class<?> returnType) {
        if (hasReturnValue(returnType)) {
            return "return null;";
        }
        return "return;";
    }


    private static boolean hasReturnValue(Class<?> returnType) {
        if (returnType == Void.class || returnType == void.class) {
            return false;
        }
        return true;
    }

    private static String toLowerFirstCase(String paramName) {
        char first = paramName.charAt(0);

        //首字母大写判断
        if (first >= 'A' && first <= 'Z') {
            first += 32;
            paramName = first + paramName.substring(1);
        }

        return paramName;
    }

}
